﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace Compiler
{
    public static class InlineCodeCompiler
    {
        public static bool Compile(string usingTask, out List<string> errorMessage)
        {
            if (string.IsNullOrWhiteSpace(usingTask))
                throw new ArgumentNullException();

            errorMessage = new List<string>();
            try
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(usingTask);
                XmlNode codeNode = xmlDoc.SelectSingleNode("/UsingTask/Task/Code");
                if (codeNode == null)
                    return true;

                string codeText = codeNode.InnerText;
                if (string.IsNullOrWhiteSpace(codeText))
                    return true;

                XmlNode usingTaskNode = xmlDoc.SelectSingleNode("/UsingTask");
                if (usingTaskNode == null)
                    return true;

                string taskName = GetAttriuteValue(usingTaskNode, "TaskName");
                if (string.IsNullOrWhiteSpace(taskName))
                {
                    errorMessage.Add("TaskName Attribute is not exist or don't set value");
                    return false;
                }

                string codeType = GetAttriuteValue(codeNode, "Type");
                if (codeType == null)
                    codeType = "Fragment";
                
                string codeLanguage = GetAttriuteValue(codeNode, "Language");
                if (codeLanguage == null)
                    codeLanguage = "cs";
                
                switch (codeLanguage.ToLower())
                {
                    case "cs":
                        codeLanguage = "CSharp";
                        break;
                    case "vb":
                        codeLanguage = "VisualBasic";
                        break;
                    default:
                        errorMessage.Add("Language attribute value is incorrect.");
                        return false;
                }

                string taskFactory = GetAttriuteValue(usingTaskNode, "TaskFactory");
                if (string.IsNullOrWhiteSpace(taskFactory) || taskFactory.ToLower() != "codetaskfactory")
                {
                    errorMessage.Add("TaskFactory Attribute is not exist or don't set value or value is not 'CodeTaskFactory'");
                    return false;
                }

                List<ParamInfo> parameters = new List<ParamInfo>();
                XmlNode paramGroupNode = xmlDoc.SelectSingleNode("/UsingTask/ParameterGroup");
                if (paramGroupNode != null)
                {
                    XmlNodeList paramNodes = paramGroupNode.ChildNodes;
                    if (paramNodes != null && paramNodes.Count > 0)
                    {
                        foreach (XmlNode p in paramNodes)
                        {
                            ParamInfo pInfo = new ParamInfo { Name = p.Name };
                            //Required
                            bool isRequired = false;
                            string requiredText = GetAttriuteValue(p, "Required");
                            if (!string.IsNullOrWhiteSpace(requiredText))
                                bool.TryParse(requiredText, out isRequired);
                            pInfo.IsRequred = isRequired;
                            //ParameterType
                            XmlAttribute paramTypeAttr = p.Attributes["ParameterType"];
                            string paramType = "System.String";
                            string paramTypeText = GetAttriuteValue(p, "ParameterType");
                            if (!string.IsNullOrWhiteSpace(paramTypeText))
                                paramType = paramTypeText;
                            pInfo.Type = paramType;
                            //Output
                            bool output = false;
                            string outputText = GetAttriuteValue(p, "Output");
                            if (!string.IsNullOrWhiteSpace(outputText))
                                bool.TryParse(outputText, out output);
                            pInfo.isOutput = output;

                            parameters.Add(pInfo);
                        }
                    }
                }

                List<string> references = new List<string>();
                XmlNodeList referenceNodes = xmlDoc.SelectNodes("/UsingTask/Task/Reference");
                if (referenceNodes != null)
                {
                    foreach (XmlNode referenceNode in referenceNodes)
                    {
                        string reference = GetAttriuteValue(referenceNode, "Include");
                        if (!string.IsNullOrWhiteSpace(reference))
                            references.Add(reference);
                    }

                }

                List<string> usings = new List<string>();
                XmlNodeList usingNodes = xmlDoc.SelectNodes("/UsingTask/Task/Using");
                if (usingNodes != null)
                {
                    foreach (XmlNode usingNode in usingNodes)
                    {
                        string usingText = GetAttriuteValue(usingNode, "Namespace");
                        if (!string.IsNullOrWhiteSpace(usingText))
                            usings.Add(usingText);
                    }
                }

                CompileType compilerType = CompileType.Fragment;
                switch (codeType.ToLower())
                {
                    case "fragment":
                        compilerType = CompileType.Fragment;
                        break;
                    case "method":
                        compilerType = CompileType.Method;
                        break;
                    case "class":
                        compilerType = CompileType.Class;
                        break;
                    default:
                        errorMessage.Add("Type attribute value is incorrect in Code elelment.");
                        return false;
                }
                Compiler.ICompiler compiler = CompilerFactory.GetCompiler(compilerType, codeLanguage);
                if (compiler != null)
                {
                    bool result = compiler.Compile(taskName, codeText, codeLanguage, parameters, references, usings, out errorMessage);
                    return result;
                }
                return true;
            }
            catch (Exception ex)
            {
                errorMessage.Add(ex.Message);
                return false;
            }
        }

        private static string GetAttriuteValue(XmlNode node, string attributeName)
        {
            string result = null;

            XmlAttribute attr = node.Attributes[attributeName];
            if (attr != null && !string.IsNullOrWhiteSpace(attr.Value))
                result = attr.Value;

            return result;
        }


    }
}
